package com.myf.services;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.myf.AppException;
import com.myf.PaginationResult;
import com.myf.Utility;
import com.myf.dtos.inventory.*;
import com.myf.entities.InventoryEntity;
import com.myf.entities.InventoryHisEntity;
import com.myf.enums.StockAction;
import com.myf.mappers.InventoryHisMapper;
import com.myf.mappers.InventoryMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.format.DateTimeFormatter;
import java.util.HashMap;

@Service
public class InventoryService {
    private final InventoryHisMapper inventoryHisMapper;
    private final InventoryMapper inventoryMapper;

    public InventoryService(InventoryHisMapper inventoryHisMapper, InventoryMapper inventoryMapper) {
        this.inventoryHisMapper = inventoryHisMapper;
        this.inventoryMapper = inventoryMapper;
    }

    private void checkWarehouseStatus(int id) throws AppException {
        var result = inventoryHisMapper.getWarehouseStatus(id);
        if (result == null) {
            throw new AppException("the warehouse could'n find");
        }
        if (result == 0) {
            throw new AppException("the warehouse is invalid");
        }
    }

    private void checkWarehouseLocationStatus(int id) throws AppException {
        var result = inventoryHisMapper.getWarehouseLocationStatus(id);
        if (result == null) {
            throw new AppException("the warehouse location could'n find");
        }
        if (result == 0) {
            throw new AppException("the warehouse location is invalid");
        }
    }

    private void checkMaterialStatus(int id) throws AppException {
        var result = inventoryHisMapper.getMatetrialStatus(id);
        if (result == null) {
            throw new AppException("the material could'n find");
        }
        if (result == 0) {
            throw new AppException("the material is invalid");
        }
    }

    @Transactional
    public void save(InventoryHisInput input, int currentUserId) throws AppException {
        // 检查仓库状态是否OK
        checkWarehouseStatus(input.warehouseId);
        // 检查库位状态是否OK
        checkWarehouseLocationStatus(input.locationId);
        // 检查物料状态是否OK
        checkMaterialStatus(input.materialId);
        // 插入库存历史表
        var his = new InventoryHisEntity();
        his.warehouseId = input.warehouseId;
        his.locationId = input.locationId;
        his.materialId = input.materialId;
        his.documentNo = input.documentNo;
        his.documentDate = input.documentDate;
        his.documentPrice = input.documentPrice;
        his.documentType = input.documentType;
        his.desc = input.desc;
        his.action = input.action;
        his.quantity = input.quantity;
        his.createAt = Utility.getCurrentUTCTime();
        his.createBy = currentUserId;
        // 更新库存表
        saveInternal(input, his, currentUserId);
        inventoryHisMapper.insert(his);
    }

    private void saveInternal(InventoryHisInput input, InventoryHisEntity inventoryHisEntity, int currentUserId) throws AppException {
        // 获取出查库存表的数据
        var queryWrapper = new QueryWrapper<InventoryEntity>();
        queryWrapper.eq("warehouse_id", input.warehouseId)
                .eq("location_id", input.locationId)
                .eq("material_id", input.materialId);
        var item = inventoryMapper.selectOne(queryWrapper);
        if (item == null) {
            if (input.action == StockAction.StockOut.getValue()) {
                // 如果是出库，检查数量是否足够
                throw new AppException("the inventory quantity is not enough");
            }
            item = new InventoryEntity();
            item.warehouseId = input.warehouseId;
            item.locationId = input.locationId;
            item.materialId = input.materialId;
            item.previousQuantity = 0;
            item.quantity = input.quantity;
            inventoryHisEntity.previousQuantity = 0;
            inventoryHisEntity.quantity = inventoryHisEntity.futureQuantity = input.quantity;
            item.createAt = inventoryHisEntity.createAt;
            item.createBy = currentUserId;
            item.version = 0;
            inventoryMapper.insert(item);
        } else {
            inventoryHisEntity.previousQuantity = item.quantity;
            int result = 0;
            if (input.action == StockAction.StockIn.getValue()) {  // 入库
                inventoryHisEntity.futureQuantity = item.quantity + input.quantity;
                var updateWrapper = new UpdateWrapper<InventoryEntity>();
                updateWrapper.eq("id", item.id)
                        .eq("row_version", item.version)
                        .set("quantity", inventoryHisEntity.futureQuantity)
                        .set("previous_quantity", item.quantity)
                        .set("update_by", currentUserId)
                        .set("update_at", inventoryHisEntity.createAt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSX")))
                        .set("row_version", item.version + 1)
                        .set(item.deleteBy != null, "delete_by", null)
                        .set(item.deleteAt != null, "delete_at", null);
                result = this.inventoryMapper.update(updateWrapper);
            } else if (input.action == StockAction.StockOut.getValue()) { // 出库
                if (item.quantity < input.quantity) {
                    // 是出库，检查数量是否足够
                    throw new AppException("the inventory quantity is not enough, current is " + item.quantity);
                }
                inventoryHisEntity.futureQuantity = item.quantity - input.quantity;
                var updateWrapper = new UpdateWrapper<InventoryEntity>();
                updateWrapper.eq("id", item.id)
                        .eq("row_version", item.version)
                        .set("quantity", inventoryHisEntity.futureQuantity)
                        .set("previous_quantity", item.quantity)
                        .set("update_by", currentUserId)
                        .set("update_at", inventoryHisEntity.createAt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSX")))
                        .set("row_version", item.version + 1)
                        .set(inventoryHisEntity.futureQuantity == 0, "delete_by", currentUserId)
                        .set(inventoryHisEntity.futureQuantity == 0, "delete_at", inventoryHisEntity.createAt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSX")));
                result = this.inventoryMapper.update(updateWrapper);
            }
            if (result == 0) {
                throw new AppException("the inventory data has changed, please try again");
            }
        }
    }

    public PaginationResult<InventoryHisQueryOutput> queryHis(InventoryHisQueryInput input) {
        var map = new HashMap<String, Object>();
        if (!input.keyword.isBlank()) {
            map.put("keyword", "%".concat(input.keyword).concat("%"));
        }
        map.put("limit", input.pageSize);
        map.put("offset", input.getOffset());
        var total = inventoryHisMapper.getPaginationCount(map);
        var rows = inventoryHisMapper.getPaginationList(map);
        return new PaginationResult<>(total, rows);
    }

    public InventoryHisQueryOutput getById(int id) {
        return inventoryHisMapper.getById(id);
    }

    public PaginationResult<InventoryQueryOutput> query(InventoryQueryInput input) {
        var map = new HashMap<String, Object>();
        if (!input.keyword.isBlank()) {
            map.put("keyword", "%".concat(input.keyword).concat("%"));
        }
        map.put("limit", input.pageSize);
        map.put("offset", input.getOffset());
        var total = inventoryMapper.getPaginationCount(map);
        var rows = inventoryMapper.getPaginationList(map);
        return new PaginationResult<>(total, rows);
    }
}
